/*
 * Copyright 2010 Alessandro Bellucci
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.soupframework.gwt.designerpanel.interaction.mouse.client;

import org.soupframework.gwt.designerpanel.client.designer.ContainerDesigner;
import org.soupframework.gwt.designerpanel.client.designer.Designer;
import org.soupframework.gwt.designerpanel.client.interaction.InteractionHandlerManager;
import org.soupframework.gwt.designerpanel.client.interaction.InteractionMode;
import org.soupframework.gwt.designerpanel.client.interaction.ResizeMode;
import org.soupframework.gwt.designerpanel.client.util.Util;

import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style.Cursor;
import com.google.gwt.event.dom.client.HasMouseMoveHandlers;
import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.user.client.ui.Widget;

public abstract class ContainerMouseDnDInteractionHandler<T, CD extends ContainerDesigner<T>> extends ContainerMouseInteractionHandler<T, CD> implements MouseMoveHandler {

	private static final int RESIZE_ZONE_SIZE = 5;

	public ContainerMouseDnDInteractionHandler(InteractionHandlerManager<T> manager, CD designer){
		super(manager, designer);
	}

	@Override
	public void registerHandlersOnTarget(){
		super.registerHandlersOnTarget();

		if(!(this.getDesigner().getManager().getDesignerPanel() instanceof HasMouseMoveHandlers)){
			throw new IllegalArgumentException("Target widget must implement HasMouseMoveHandlers interface");
		}

		((HasMouseMoveHandlers)this.getDesigner().getManager().getDesignerPanel()).addMouseMoveHandler(this);
	}

	@Override
	public void onMouseMove(MouseMoveEvent event) {
		event.preventDefault();
		this.dispatchEvent(event);
	}

	@Override
	protected boolean dispatchEventInt(GwtEvent<?> event, Designer<T> childDesigner) {
		if(event instanceof MouseMoveEvent){
			return this.onMouseMoveInt((MouseMoveEvent)event, childDesigner);
		}
		return super.dispatchEventInt(event, childDesigner);
	}

	protected boolean onMouseMoveInt(MouseMoveEvent event, Designer<T> childDesigner) {
		if(childDesigner != null){
			return false;
			// MouseMove events are only handled by foremost designers unless nothing has been selected in the child designer or in its descendants
			/*if(childDesigner instanceof ContainerDesigner){
				if(((ContainerDesigner<T>)childDesigner).getSelectedObjectsCount(true) > 0){
					return;
				}
			}*/
		}

		//System.out.println(this.getManager().getInteractionMode() + " onMouseMove [" + mouseDown + "]: " + this.getDesigner());

		// check if the button is not up any more even if the MouseUp event was not fired
		if((event.getNativeButton() & NativeEvent.BUTTON_LEFT) == 0){
			mouseDown = false;
		}

		if(mouseDown){
			if( this.getManager().getInteractionMode() == InteractionMode.SELECT &&
				(mouseX != mouseDownX || mouseY != mouseDownY)
			) {
				// If mouse moved since the MouseDown event set interaction mode to move
				this.getManager().setInteractionMode(InteractionMode.MOVE);
			}

			if(marquee){
				this.getDesigner().getManager().getDesignerPanel().showMarquee(this.marqueeStartXRelative, this.marqueeStartYRelative, this.mouseXRelative, this.mouseYRelative);
			}
			else{
				if(this.getManager().getInteractionMode() == InteractionMode.RESIZE){
					// Only the owner container designer must handle the resize event
					if(this.getDesigner().hasChildObject(this.getManager().getResizingObject())){
						resize(event, this.getManager().getResizingObject(), this.getManager().getResizeMode());
					}
					else {
						return false; // let the parent handle this event
					}
				}
				else if(this.getManager().getInteractionMode() == InteractionMode.MOVE){
					// when control key is pressed selection is processed in the on mouse up event handler,
					// but when mouse move widgets under the cursor have to be selected
					if(ctrlKey){
						T object = this.getDesigner().getForemostObjectAtCoords(mouseX, mouseY);
						this.getDesigner().addObjectToSelection(object);
					}

					if(!drag(event, false, true)) {
						return false; // let the parent handle this event
					}
				}
			}

			mouseDownX = event.getClientX();
			mouseDownY = event.getClientY();
		}
		else{
			if(this.getManager().getInteractionMode() == InteractionMode.SELECT || this.getManager().getInteractionMode() == InteractionMode.RESIZE){
				T object = this.getDesigner().getForemostObjectAtCoords(mouseX, mouseY);
				Widget w = object != null ? this.getDesigner().getChildObjectView(object) : null;
				if(w != null && w.getParent() != null && this.getDesigner().isObjectSelected(object)){
					int left = w.getAbsoluteLeft() - Util.getTotalWidgetScrollLeft(w);
					int top = w.getAbsoluteTop() - Util.getTotalWidgetScrollTop(w);
					int right = left + w.getOffsetWidth();
					int bottom = top + w.getOffsetHeight();

					/*if(Math.abs(left - this.mouseX) <= RESIZE_ZONE_SIZE && Math.abs(top - this.mouseY) <= RESIZE_ZONE_SIZE){
						this.setCursor(Cursor.NW_RESIZE);
						this.getManager().setInteractionMode(InteractionMode.RESIZE);
						this.getManager().setResizeMode(ResizeMode.NW);
						this.getManager().setResizingWidget(w);
					}
					else if(Math.abs(right - this.mouseX) <= RESIZE_ZONE_SIZE && Math.abs(top - this.mouseY) <= RESIZE_ZONE_SIZE){
						this.setCursor(Cursor.NE_RESIZE);
						this.getManager().setInteractionMode(InteractionMode.RESIZE);
						this.getManager().setResizeMode(ResizeMode.NE);
						this.getManager().setResizingWidget(w);
					}
					else if(Math.abs(left - this.mouseX) <= RESIZE_ZONE_SIZE && Math.abs(bottom - this.mouseY) <= RESIZE_ZONE_SIZE){
						this.setCursor(Cursor.SW_RESIZE);
						this.getManager().setInteractionMode(InteractionMode.RESIZE);
						this.getManager().setResizeMode(ResizeMode.SW);
						this.getManager().setResizingWidget(w);
					}*/
					if(Math.abs(right - mouseX) <= RESIZE_ZONE_SIZE && Math.abs(bottom - mouseY) <= RESIZE_ZONE_SIZE){
						this.setCursor(Cursor.SE_RESIZE);
						this.getManager().setInteractionMode(InteractionMode.RESIZE);
						this.getManager().setResizeMode(ResizeMode.SE);
						this.getManager().setResizingObject(object);
					}
					/*else if(this.mouseX >= left && this.mouseX <= right && Math.abs(top - this.mouseY) <= RESIZE_ZONE_SIZE){
						this.setCursor(Cursor.N_RESIZE);
						this.getManager().setInteractionMode(InteractionMode.RESIZE);
						this.getManager().setResizeMode(ResizeMode.N);
						this.getManager().setResizingWidget(w);
					}*/
					else if(mouseX >= left && mouseX <= right && Math.abs(bottom - mouseY) <= RESIZE_ZONE_SIZE){
						this.setCursor(Cursor.S_RESIZE);
						this.getManager().setInteractionMode(InteractionMode.RESIZE);
						this.getManager().setResizeMode(ResizeMode.S);
						this.getManager().setResizingObject(object);
					}
					/*
					else if(this.mouseY >= top && this.mouseY <= bottom && Math.abs(left - this.mouseX) <= RESIZE_ZONE_SIZE){
						this.setCursor(Cursor.W_RESIZE);
						this.getManager().setInteractionMode(InteractionMode.RESIZE);
						this.getManager().setResizeMode(ResizeMode.W);
						this.getManager().setResizingWidget(w);
					}
					*/
					else if(mouseY >= top && mouseY <= bottom && Math.abs(right - mouseX) <= RESIZE_ZONE_SIZE){
						this.setCursor(Cursor.E_RESIZE);
						this.getManager().setInteractionMode(InteractionMode.RESIZE);
						this.getManager().setResizeMode(ResizeMode.E);
						this.getManager().setResizingObject(object);
					}
					else{
						this.setCursor(Cursor.DEFAULT);
						this.getManager().setInteractionMode(InteractionMode.SELECT);
						this.getManager().setResizeMode(null);
						this.getManager().setResizingObject(null);
					}
				}
				else{
					if(this.getDesigner().getParent() != null) {
						return false; // let the parent handle this event
					}
					else{
						this.setCursor(Cursor.DEFAULT);
						this.getManager().setInteractionMode(InteractionMode.SELECT);
						this.getManager().setResizeMode(null);
						this.getManager().setResizingObject(null);
					}
				}
			}
		}
		return true;
	}

	@Override
	protected boolean onMouseUpInt(MouseUpEvent event, Designer<T> childDesigner) {
		if(childDesigner != null){
			// MouseUp events are only handled by foremost designers unless nothing has been selected in the child designer or in its descendants
			if(childDesigner instanceof ContainerDesigner<?>){
				if(((ContainerDesigner<T>)childDesigner).getSelectedObjectsCount(true) > 0){
					return false;
				}
			}
		}

		//System.out.println(this.getManager().getInteractionMode() + " onMouseUp: " + this.getDesigner());

		((MouseInteractionHandlerManager<T>)this.getManager()).hideDragProxy();

		if(this.getManager().getInteractionMode() == InteractionMode.MOVE){
			if(!drop(event)){
				return false; // let brothers handle the event
			}
		}

		super.onMouseUpInt(event, childDesigner);

		if(this.getManager().getInteractionMode() == InteractionMode.MOVE){
			this.getManager().setInteractionMode(InteractionMode.SELECT);
		}

		return true;
	}

	/**
	 * Drags selection. Returns true if something has been dragged.
	 *
	 * @param event The mouse event
	 * @param forceMove true if move should be forced
	 * @param blockGridAlignment true if objects should be aligned to grid
	 * @return
	 */
	protected boolean drag(MouseEvent<? extends EventHandler> event, boolean forceMove, boolean blockGridAlignment){
		return dropPreview(event);
	}

	protected boolean dropPreview(MouseEvent<? extends EventHandler> event) {
		boolean ret = false;
		// if current designer's widget is not selected
		if(this.getDesigner().getParent() == null || !this.getDesigner().getParent().isObjectSelected(this.getDesigner().getObject())){
			// if the mouse is over a new container removes selected widgets from original container and adds them to the new container
			ContainerDesigner<T> selectionRootDesigner = this.getManager().getSelectionRootDesigner();
			if(selectionRootDesigner != null){
				if(this.getDesigner() != selectionRootDesigner){
					for(T object : selectionRootDesigner.getSelectedObjects(false)){
						if(this.getDesigner().isDropAllowed(object, this.mouseXRelative, this.mouseYRelative)){
							((MouseInteractionHandlerManager<T>)this.getManager()).showDragProxy(this.getDesigner(), object, selectionRootDesigner.getChildObjectView(object), this.mouseXRelative, this.mouseYRelative);
							ret = true;
						}
						else{
							this.setCursor("no-drop");
							this.setCursor("not-allowed");
							return true;
						}
					}
				}
				else{
					((MouseInteractionHandlerManager<T>)this.getManager()).hideDragProxy();
				}
			}
		}
		this.setCursor(Cursor.DEFAULT);
		return ret;
	}

	protected boolean drop(MouseEvent<? extends EventHandler> event) {
		// if current designer's widget is not selected
		if(this.getDesigner().getParent() == null || !this.getDesigner().getParent().isObjectSelected(this.getDesigner().getObject())){
			// if the mouse is over a new container removes selected widgets from original container and adds them to the new container
			ContainerDesigner<T> selectionRootDesigner = this.getManager().getSelectionRootDesigner();
			if(selectionRootDesigner != null && this.getDesigner() != selectionRootDesigner){
				for(T object : selectionRootDesigner.getSelectedObjects(false)){
					if(this.getDesigner().acceptDrop(object, this.mouseXRelative, this.mouseYRelative)){
						this.getDesigner().moveFromContainer(selectionRootDesigner, object, this.mouseXRelative, this.mouseYRelative);
						/*this.getDesignerManager().beginCommandGroup("ChangeParentCommandGroupType");
						selectionRootDesigner.removeWidget(w);
						this.getDesigner().addWidget(w, this.mouseXRelative, this.mouseYRelative);
						this.getDesignerManager().endCommandGroup("ChangeParentCommandGroupType");*/
					}
				}
				return true;
			}
			else{
				return true;
			}
		}
		return false; // let brothers handle the event
	}

	protected void resize(MouseEvent<? extends EventHandler> event, T resizingObject, ResizeMode resizeMode){
		int deltaX = mouseX - mouseDownX;
		int deltaY = mouseY - mouseDownY;
		int newWidth = Util.getWidgetStyleWidth(this.getDesigner().getChildObjectView(resizingObject));
		int newHeight = Util.getWidgetStyleHeight(this.getDesigner().getChildObjectView(resizingObject));

		if(resizeMode == ResizeMode.E || resizeMode == ResizeMode.SE || resizeMode == ResizeMode.NE){
			newWidth += deltaX;
			if(newWidth < 1){
				newWidth = 1;
			}
		}
		if(resizeMode == ResizeMode.S || resizeMode == ResizeMode.SE || resizeMode == ResizeMode.SW){
			newHeight += deltaY;
			if(newHeight < 1){
				newHeight = 1;
			}
		}

		this.getDesigner().resizeObjectPartial("" + this.mouseDownEventCount, resizingObject, newWidth, newHeight);
	}

	private void setCursor(String cursor){
		this.getDesignerManager().getDesignerPanel().setCursor(cursor);
	}

	private void setCursor(Cursor cursor){
		this.getDesignerManager().getDesignerPanel().setCursor(cursor);
	}
}
